package com.zbkj.common.utils;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.URLEncoder;

/**
 * 文件下载工具类
 * 提供安全的文件名处理和流式下载功能
 */
public class FileDownloadUtil {
    
    /**
     * 安全的文件名处理
     * 移除非法字符并进行URL编码
     * 
     * @param fileName 原始文件名
     * @return 处理后的安全文件名
     */
    public static String sanitizeFileName(String fileName) {
        if (fileName == null || fileName.trim().isEmpty()) {
            return "download";
        }
        
        // 移除CR、LF、制表符等控制字符
        fileName = fileName.replaceAll("[\\r\\n\\t]", "");
        
        // 移除Windows文件系统不允许的字符
        fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
        
        // 移除其他可能导致问题的字符
        fileName = fileName.replaceAll("[\\x00-\\x1f\\x7f]", "");
        
        // 确保文件名不为空
        if (fileName.trim().isEmpty()) {
            return "download";
        }
        
        return fileName.trim();
    }
    
    /**
     * 生成安全的Content-Disposition头
     * 
     * @param fileName 文件名
     * @return Content-Disposition头字符串
     */
    public static String generateContentDisposition(String fileName) {
        String safeFileName = sanitizeFileName(fileName);
        
        // 确保文件名有后缀
        if (!safeFileName.contains(".")) {
            // 如果没有后缀，添加默认后缀
            safeFileName = safeFileName + ".file";
        }
        
        // 检查是否包含中文或其他非ASCII字符
        boolean containsNonAscii = safeFileName.chars().anyMatch(ch -> ch > 127);
        
        if (containsNonAscii) {
            // 包含中文或特殊字符，使用URL编码
            try {
                String encodedFileName = URLEncoder.encode(safeFileName, "UTF-8");
                // 将编码后的空格替换回%20
                encodedFileName = encodedFileName.replace("+", "%20");
                
                // 生成英文后备文件名
                String englishFileName = generateEnglishFileName(safeFileName);
                
                // 同时提供两种格式：URL编码的中文文件名和英文后备文件名
                // 现代浏览器会优先使用filename*，老浏览器会使用filename
                return "attachment; filename=\"" + englishFileName + "\"; filename*=UTF-8''" + encodedFileName;
            } catch (Exception e) {
                // 编码失败，使用英文后备文件名
                String englishFileName = generateEnglishFileName(safeFileName);
                return "attachment; filename=\"" + englishFileName + "\"";
            }
        } else {
            // 纯ASCII字符，使用简单格式
            return "attachment; filename=\"" + safeFileName + "\"";
        }
    }
    
    /**
     * 为中文文件名生成英文后备文件名
     * 
     * @param fileName 原始文件名
     * @return 英文后备文件名
     */
    private static String generateEnglishFileName(String fileName) {
        if (fileName == null || fileName.trim().isEmpty()) {
            return "download.file";
        }
        
        // 提取文件扩展名
        String extension = "";
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex > 0) {
            extension = fileName.substring(lastDotIndex);
        }
        
        // 生成时间戳
        String timestamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
        
        // 生成英文文件名：download_时间戳.扩展名
        return "download_" + timestamp + extension;
    }
    
    /**
     * 生成兼容的Content-Disposition头（推荐用于中文文件名）
     * 使用更兼容的方式处理中文文件名
     * 
     * @param fileName 文件名
     * @return Content-Disposition头字符串
     */
    public static String generateCompatibleContentDisposition(String fileName) {
        String safeFileName = sanitizeFileName(fileName);
        
        // 确保文件名有后缀
        if (!safeFileName.contains(".")) {
            // 如果没有后缀，添加默认后缀
            safeFileName = safeFileName + ".file";
        }
        
        // 检查是否包含中文或其他非ASCII字符
        boolean containsNonAscii = safeFileName.chars().anyMatch(ch -> ch > 127);
        
        if (containsNonAscii) {
            // 对于中文文件名，使用Base64编码（更兼容的方式）
            try {
                String base64FileName = java.util.Base64.getEncoder().encodeToString(safeFileName.getBytes("UTF-8"));
                return "attachment; filename=\"" + safeFileName + "\"; filename*=UTF-8''" + base64FileName;
            } catch (Exception e) {
                // 编码失败，使用简单格式
                return "attachment; filename=\"" + safeFileName + "\"";
            }
        } else {
            // 纯ASCII字符，使用简单格式
            return "attachment; filename=\"" + safeFileName + "\"";
        }
    }
    
    /**
     * 生成简单的Content-Disposition头（推荐用于避免中文编码问题）
     * 直接使用英文文件名，避免浏览器兼容性问题
     * 
     * @param fileName 文件名
     * @return Content-Disposition头字符串
     */
    public static String generateSimpleContentDisposition(String fileName) {
        String safeFileName = sanitizeFileName(fileName);
        
        // 确保文件名有后缀
        if (!safeFileName.contains(".")) {
            // 如果没有后缀，添加默认后缀
            safeFileName = safeFileName + ".file";
        }
        
        // 检查是否包含中文或其他非ASCII字符
        boolean containsNonAscii = safeFileName.chars().anyMatch(ch -> ch > 127);
        
        if (containsNonAscii) {
            // 包含中文，使用英文文件名
            String englishFileName = generateEnglishFileName(safeFileName);
            return "attachment; filename=\"" + englishFileName + "\"";
        } else {
            // 纯ASCII字符，使用原始文件名
            return "attachment; filename=\"" + safeFileName + "\"";
        }
    }
    
    /**
     * 流式下载文件到HTTP响应
     * 
     * @param inputStream 文件输入流
     * @param fileName 文件名
     * @param contentType 内容类型
     * @param response HTTP响应对象
     * @throws IOException 如果下载过程中发生IO异常
     */
    public static void downloadFile(InputStream inputStream, String fileName, String contentType, HttpServletResponse response) throws IOException {
        downloadFile(inputStream, fileName, contentType, response, 1024);
    }
    
    /**
     * 流式下载文件到HTTP响应
     * 
     * @param inputStream 文件输入流
     * @param fileName 文件名
     * @param contentType 内容类型
     * @param response HTTP响应对象
     * @param bufferSize 缓冲区大小
     * @throws IOException 如果下载过程中发生IO异常
     */
    public static void downloadFile(InputStream inputStream, String fileName, String contentType, HttpServletResponse response, int bufferSize) throws IOException {
        if (inputStream == null) {
            throw new IllegalArgumentException("输入流不能为空");
        }
        
        if (response == null) {
            throw new IllegalArgumentException("响应对象不能为空");
        }
        
        OutputStream outputStream = null;
        try {
            // 设置响应头
            response.setContentType(contentType != null ? contentType : "application/octet-stream");
            
            // 生成Content-Disposition头
            String contentDisposition = generateContentDisposition(fileName);
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-Disposition", contentDisposition);


            
            // 禁用缓存
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");
            
            // 流式传输
            outputStream = response.getOutputStream();
            byte[] buffer = new byte[bufferSize];
            int bytesRead;
            
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            
            outputStream.flush();
            
        } finally {
            // 关闭资源
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    // 记录但不抛出异常
                    System.err.println("关闭输出流时发生异常: " + e.getMessage());
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // 记录但不抛出异常
                    System.err.println("关闭输入流时发生异常: " + e.getMessage());
                }
            }
        }
    }
    
    /**
     * 根据文件扩展名获取内容类型
     * 
     * @param fileName 文件名
     * @return 内容类型
     */
    public static String getContentType(String fileName) {
        if (fileName == null) {
            return "application/octet-stream";
        }
        
        String extension = "";
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex > 0) {
            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
        }
        
        switch (extension) {
            case "pdf":
                return "application/pdf";
            case "doc":
                return "application/msword";
            case "docx":
                return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            case "xls":
                return "application/vnd.ms-excel";
            case "xlsx":
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            case "ppt":
                return "application/vnd.ms-powerpoint";
            case "pptx":
                return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
            case "txt":
                return "text/plain";
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            case "gif":
                return "image/gif";
            case "mp4":
                return "video/mp4";
            case "mp3":
                return "audio/mpeg";
            case "zip":
                return "application/zip";
            case "rar":
                return "application/x-rar-compressed";
            default:
                return "application/octet-stream";
        }
    }
    
    /**
     * 测试文件名处理功能
     * 用于验证文件名清理和Content-Disposition生成是否正确
     */
    public static void testFileNameProcessing() {
        System.out.println("=== 文件名处理测试 ===");
        
        String[] testFileNames = {
            "normal-file.pdf",
            "file with spaces.txt",
            "file\nwith\nnewlines.doc",
            "file\r\nwith\r\ncrlf.docx",
            "file\twith\ttabs.xls",
            "file/with/slashes.jpg",
            "file\\with\\backslashes.png",
            "file:with:colons.mp4",
            "file*with*asterisks.mp3",
            "file?with?questionmarks.zip",
            "file\"with\"quotes.rar",
            "file<with>brackets.txt",
            "file|with|pipes.pdf",
            "中文文件名.pdf",
            "文件名 包含 空格.txt",
            "测试123.pdf",
            "测试文件.docx",
            "中文文件名包含特殊字符：测试.pdf",
            "filename_without_extension",  // 没有后缀的文件名
            "中文文件名无后缀",  // 中文文件名无后缀
            "",  // 空文件名
            null, // null文件名
            "   ", // 只有空格的文件名
            "file.with.multiple.dots.txt",
            "FILE.WITH.UPPERCASE.PDF",
            "测试文件123.docx",
            "测试文件_123.pdf"
        };
        
        for (String fileName : testFileNames) {
            System.out.println("\n原始文件名: " + (fileName == null ? "null" : "\"" + fileName + "\""));
            
            String sanitized = sanitizeFileName(fileName);
            System.out.println("清理后文件名: \"" + sanitized + "\"");
            
            String contentDisposition = generateContentDisposition(fileName);
            System.out.println("Content-Disposition (完整版): " + contentDisposition);
            
            String simpleContentDisposition = generateSimpleContentDisposition(fileName);
            System.out.println("Content-Disposition (简化版): " + simpleContentDisposition);
            
            String contentType = getContentType(fileName);
            System.out.println("Content-Type: " + contentType);
        }
    }
    
    /**
     * 测试中文文件名下载功能
     * 用于验证中文文件名是否正确处理
     */
    public static void testChineseFileNameDownload() {
        System.out.println("=== 中文文件名下载测试 ===");
        
        String[] chineseFileNames = {
            "测试123.pdf",
            "中文文件名.docx",
            "测试文件_2024.xlsx",
            "文档.pdf",
            "图片.jpg",
            "视频.mp4",
            "音频.mp3",
            "压缩包.zip",
            "测试文件包含空格 123.txt",
            "测试文件包含特殊字符：测试.pdf"
        };
        
        for (String fileName : chineseFileNames) {
            System.out.println("\n原始文件名: " + fileName);
            
            String sanitized = sanitizeFileName(fileName);
            System.out.println("清理后文件名: " + sanitized);
            
            // 测试URL编码
            try {
                String encodedFileName = URLEncoder.encode(sanitized, "UTF-8");
                encodedFileName = encodedFileName.replace("+", "%20");
                System.out.println("URL编码后: " + encodedFileName);
                
                // 测试解码
                String decodedFileName = java.net.URLDecoder.decode(encodedFileName, "UTF-8");
                System.out.println("URL解码后: " + decodedFileName);
            } catch (Exception e) {
                System.out.println("URL编码失败: " + e.getMessage());
            }
            
            String contentDisposition = generateContentDisposition(fileName);
            System.out.println("Content-Disposition: " + contentDisposition);
            
            // 解析Content-Disposition头
            String filename = extractFilenameFromContentDisposition(contentDisposition);
            System.out.println("解析出的文件名: " + filename);
            
            String contentType = getContentType(fileName);
            System.out.println("Content-Type: " + contentType);
        }
    }
    
    /**
     * 从Content-Disposition头中提取文件名
     * 
     * @param contentDisposition Content-Disposition头字符串
     * @return 提取的文件名
     */
    private static String extractFilenameFromContentDisposition(String contentDisposition) {
        if (contentDisposition == null || contentDisposition.isEmpty()) {
            return null;
        }
        
        // 优先提取filename*参数（UTF-8编码的文件名）
        if (contentDisposition.contains("filename*=UTF-8''")) {
            try {
                String encodedPart = contentDisposition.split("filename*=UTF-8''")[1];
                // 提取到下一个分号或结束
                String encodedFileName = encodedPart.split(";")[0];
                return java.net.URLDecoder.decode(encodedFileName, "UTF-8");
            } catch (Exception e) {
                // 解码失败，继续尝试其他方法
            }
        }
        
        // 提取filename参数
        if (contentDisposition.contains("filename=\"")) {
            try {
                String filenamePart = contentDisposition.split("filename=\"")[1];
                return filenamePart.split("\"")[0];
            } catch (Exception e) {
                // 提取失败
            }
        }
        
        return null;
    }
    
    /**
     * 生成测试HTML页面
     * 用于在浏览器中测试中文文件名下载
     */
    public static String generateTestHtml() {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>\n");
        html.append("<html>\n");
        html.append("<head>\n");
        html.append("    <meta charset=\"UTF-8\">\n");
        html.append("    <title>中文文件名下载测试</title>\n");
        html.append("    <style>\n");
        html.append("        body { font-family: Arial, sans-serif; margin: 20px; }\n");
        html.append("        .test-item { margin: 10px 0; padding: 10px; border: 1px solid #ddd; }\n");
        html.append("        button { padding: 5px 10px; margin: 5px; }\n");
        html.append("    </style>\n");
        html.append("</head>\n");
        html.append("<body>\n");
        html.append("    <h1>中文文件名下载测试</h1>\n");
        html.append("    <div class=\"test-item\">\n");
        html.append("        <h3>测试文件列表：</h3>\n");
        html.append("        <button onclick=\"testDownload('测试123.pdf')\">测试123.pdf</button>\n");
        html.append("        <button onclick=\"testDownload('中文文件名.docx')\">中文文件名.docx</button>\n");
        html.append("        <button onclick=\"testDownload('测试文件_2024.xlsx')\">测试文件_2024.xlsx</button>\n");
        html.append("        <button onclick=\"testDownload('文档.pdf')\">文档.pdf</button>\n");
        html.append("        <button onclick=\"testDownload('图片.jpg')\">图片.jpg</button>\n");
        html.append("    </div>\n");
        html.append("    \n");
        html.append("    <div class=\"test-item\">\n");
        html.append("        <h3>URL编码测试：</h3>\n");
        html.append("        <input type=\"text\" id=\"testFileName\" placeholder=\"输入中文文件名\" value=\"测试123.pdf\">\n");
        html.append("        <button onclick=\"testUrlEncode()\">测试URL编码</button>\n");
        html.append("        <div id=\"urlEncodeResult\"></div>\n");
        html.append("    </div>\n");
        html.append("    \n");
        html.append("    <script>\n");
        html.append("        function testDownload(fileName) {\n");
        html.append("            console.log('测试下载文件名:', fileName);\n");
        html.append("            \n");
        html.append("            // 模拟Content-Disposition头\n");
        html.append("            const encodedFileName = encodeURIComponent(fileName);\n");
        html.append("            const contentDisposition = `attachment; filename*=UTF-8''${encodedFileName}`;\n");
        html.append("            \n");
        html.append("            console.log('Content-Disposition:', contentDisposition);\n");
        html.append("            \n");
        html.append("            // 创建测试下载\n");
        html.append("            const blob = new Blob(['测试文件内容'], {type: 'text/plain'});\n");
        html.append("            const url = URL.createObjectURL(blob);\n");
        html.append("            const a = document.createElement('a');\n");
        html.append("            a.href = url;\n");
        html.append("            a.download = fileName;\n");
        html.append("            a.click();\n");
        html.append("            URL.revokeObjectURL(url);\n");
        html.append("            \n");
        html.append("            alert('下载测试完成，请检查下载的文件名是否正确显示中文');\n");
        html.append("        }\n");
        html.append("        \n");
        html.append("        function testUrlEncode() {\n");
        html.append("            const fileName = document.getElementById('testFileName').value;\n");
        html.append("            const encoded = encodeURIComponent(fileName);\n");
        html.append("            const decoded = decodeURIComponent(encoded);\n");
        html.append("            \n");
        html.append("            const result = `\n");
        html.append("                <p><strong>原始文件名:</strong> ${fileName}</p>\n");
        html.append("                <p><strong>URL编码后:</strong> ${encoded}</p>\n");
        html.append("                <p><strong>URL解码后:</strong> ${decoded}</p>\n");
        html.append("                <p><strong>Content-Disposition:</strong> attachment; filename*=UTF-8''${encoded}</p>\n");
        html.append("            `;\n");
        html.append("            \n");
        html.append("            document.getElementById('urlEncodeResult').innerHTML = result;\n");
        html.append("        }\n");
        html.append("    </script>\n");
        html.append("</body>\n");
        html.append("</html>");
        
        return html.toString();
    }
} 